﻿using NullCommander.Properties;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace NullCommander
{
    public partial class mainForm : Form
    {
        // Адресная строка.
        private ToolStripSpringTextBox editNavigate;
        // Флаг, который показывает, что узел раскрывается программно.
        private Boolean programmExpand;
        // История навигации.
        private NavigateHistory history;
        // Слежение за файлами в текущей директории.
        private FileSystemWatcher fileWatcher;
        // Список файлов для копирования/перемещения.
        private List<ShortFileInfo> clipbList;
        // Текущая операция с файлами.
        private FileOperation fileOperation;


        public mainForm()
        {
            InitializeComponent();
            editNavigate = new ToolStripSpringTextBox();
            editNavigate.ToolTipText = Resources.ADDRESS_PATH;
            editNavigate.KeyPress += editNavigate_KeyPress;
            toolNavigate.Items.Add(editNavigate);

            history = new NavigateHistory();
            history.OnNavigateChange += OnNavigateChange;

            clipbList = new List<ShortFileInfo>();

            itemSortName.Checked = true;

            CanCreateNewElement(false);
            CanCopyCutDelete(false);
            CanPaste(false);
            CanShowInfo(false);
        }

        private void mainForm_Load(object sender, EventArgs e)
        {
            // Загружаем в дерево логические диски.
            var drives = Directory.GetLogicalDrives();
            foreach (var s in drives)
            {
                LoadAssociatedIcon(imageList16, s, s, IconSize.Small);
                var node = treeView.Nodes.Add(String.Empty, s.ExcludePathDelimiter(), s, s);
                // Если в директории есть хотя бы одна поддиректория, у узла надо отобразить плюсик.
                if (DirectoryHasSubdir(s))
                    node.Nodes.Add(String.Empty);
                try
                {
                    var watcher = new FileSystemWatcher(s, "*");

                    watcher.SynchronizingObject = this;

                    watcher.NotifyFilter = NotifyFilters.DirectoryName;
                    watcher.IncludeSubdirectories = true;
                    watcher.Created += fileSystemWatcherFolder_Changed;
                    watcher.Deleted += fileSystemWatcherFolder_Changed;
                    watcher.Renamed += fileSystemWatcherFolder_Renamed;
                    watcher.EnableRaisingEvents = true;
                }
                catch (ArgumentException)
                {
                }
            }

            fileWatcher = new FileSystemWatcher();
            fileWatcher.Filter = "*";
            fileWatcher.SynchronizingObject = this;
            fileWatcher.NotifyFilter = NotifyFilters.FileName;
            fileWatcher.IncludeSubdirectories = false;
            fileWatcher.Created += fileSystemWatcherFile_Changed;
            fileWatcher.Deleted += fileSystemWatcherFile_Changed;
            //fileWatcher.Changed += fileSystemWatcherFile_Changed;
            fileWatcher.Renamed += fileSystemWatcherFile_Renamed;
            fileWatcher.EnableRaisingEvents = false;
        }

        /// <summary>
        /// Загружает иконку, ассоциированную с файлом в имейдж лист.
        /// </summary>
        /// <param name="imgList"></param>
        /// <param name="path"></param>
        /// <param name="key"></param>
        /// <param name="size"></param>
        private void LoadAssociatedIcon(ImageList imgList, String path, String key, IconSize size)
        {
            if (!imgList.Images.ContainsKey(key))
            {
                Icon icon;
                try
                {
                    icon = IconExtractor.GetFileIcon(path, size);
                    //icon = Icon.ExtractAssociatedIcon(path);
                }
                catch (ArgumentException)
                {
                    icon = SystemIcons.Application;
                }
                catch (IOException)
                {
                    icon = SystemIcons.Application;
                }
                if (icon != null)
                    imgList.Images.Add(key, icon);
            }
        }

        /// <summary>
        /// Метод получаем путь к иконке, ассоциированной с папкой.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        private String GetDirIconPath(String path)
        {
            var result = String.Empty;
            path = path.IncludePathDelimiter() + "desktop.ini";
            if (File.Exists(path))
                result = IniFile.ReadString(path, ".ShellClassInfo", "IconResource");
            return String.IsNullOrEmpty(result) ? "_default_folder_" : result;
        }

        /// <summary>
        /// Добавление нового узла в дерево.
        /// </summary>
        /// <param name="parent"></param>
        /// <param name="text"></param>
        /// <returns></returns>
        private TreeNode AddNode(TreeNodeCollection parent, DirectoryInfo dir)
        {
            // Проверяем включена ли опция показа скрытых файлов, либо файл нескрытый.
            if (!toolBtnShowHidden.Checked && (dir.Attributes & FileAttributes.Hidden) != 0)
                return null;
            // Получаем путь к иконке для папки.
            var key = GetDirIconPath(dir.FullName);
            // Загружаем иконку в имедж лист. 
            LoadAssociatedIcon(imageList16, dir.FullName, key, IconSize.Small);
            // Добавляем нод.
            var node = parent.Add(String.Empty, dir.Name, key, key);
            // Если в директории есть хотя бы одна поддиректория, у узла надо отобразить плюсик.
            if (DirectoryHasSubdir(dir.FullName))
                node.Nodes.Add(String.Empty);
            return node;
        }

        // http://msdn.microsoft.com/ru-ru/library/system.collections.ienumerable.getenumerator.aspx
        // http://msdn.microsoft.com/ru-ru/library/aa664754(v=vs.71).aspx
        /// <summary>
        /// Метод определяет, есть ли в директории хотя бы одна поддиректория.
        /// </summary>
        /// <param name="path">Путь к директории.</param>
        /// <returns></returns>
        private static Boolean DirectoryHasSubdir(String path) 
        {
            return true;
            //TODO: разобраться с быстродействием.
            try
            {
                if (!Directory.Exists(path))
                    return false;
                // Получаем перечислитель для указанной директориии.
                var enumerator = Directory.EnumerateDirectories(path).GetEnumerator();
                if (enumerator == null)
                    return false;
                try
                {
                    // Если есть хотя бы один элемент, то возвращаем true.
                    return enumerator.MoveNext();
                }
                finally
                {
                    var dispose = enumerator as IDisposable;
                    if (dispose != null)
                        dispose.Dispose();
                }
            }
            catch (ArgumentException)
            {
                return false;
            }
            catch (UnauthorizedAccessException)
            {
                return false;
            }
            //catch (PathTooLongException)
            //{
            //    return false;
            //}
            catch (IOException)
            {
                return false;
            }
        }

        /// <summary>
        /// Определяем тип файла по указанном пути.
        /// </summary>
        /// <param name="path">Путь файла.</param>
        /// <returns>Возвращает одно из трех значений: файл, папка или ошибка.</returns>
        private PathType GetPathType(String path)
        {
            if (File.Exists(path))
                return PathType.File;
            else if (Directory.Exists(path))
                return PathType.Directory;
            else
                return PathType.Error;
        }

        /// <summary>
        /// Получение нода по указанному пути.
        /// </summary>
        /// <param name="path">Путь. Если указан относительный путь, то в этой переменной будет полный путь</param>
        /// <param name="currentPath">Текущий путь. Будет использован, если path - относительный путь.</param>
        /// <returns>Нода или null в случае ошибки.</returns>
        private TreeNode GetNodeByPath(String path, Boolean forceInit)
        {
            treeView.BeginUpdate();
            try
            {
                var dirs = path.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
                // Не удалось разобрать путь на составляющие.
                if (dirs.Length == 0)
                    return null;
                //
                var tempPath = String.Empty;
                //
                var nodes = treeView.Nodes;
                TreeNode node = null;
                // Перебираем директории. 
                foreach (var dir in dirs)
                {
                    // Ищем нод, соотвествующий текущей директории.
                    node = nodes.NodeByText(dir);
                    if (node == null)
                        return null;
                    nodes = node.Nodes;
                    // Текущий путь.
                    tempPath = Path.Combine(tempPath, dir) + Path.DirectorySeparatorChar;
                    // Необходимо заполнить нод.
                    if (forceInit && !node.IsInit())
                    {
                        nodes.Clear();
                        // Загружаем субдиректории.
                        var dirInfo = new DirectoryInfo(tempPath);
                        foreach (var info in dirInfo.GetDirectories())
                            AddNode(nodes, info);
                        node.Tag = true;
                    }
                }
                return node;
            }
            catch (ArgumentException)
            {
                return null;
            }
            catch (UnauthorizedAccessException)
            {
                return null;
            }
            catch (IOException)
            {
                return null;
            }
            finally
            {
                treeView.EndUpdate();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        private void OpenFolder(String path)
        {
            var counter = new Stopwatch();
            counter.Start();

            // Получаем узел, который соотвествует указанному пути.
            var node = GetNodeByPath(path, true);
            if (node == null)
            {
                MessageBox.Show(String.Format(Resources.ERROR_GOTO_PATH, path), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                history.Back();
                return;
            }
            
            // Запрещаем узлу реагировать на открытие.
            programmExpand = true;
            try
            {
                // Выделяем итоговый нод.
                treeView.SelectedNode = node;
                //treeViewFolders.SelectedNode.Expand();
                // Проматываем дерево до него.
                treeView.SelectedNode.EnsureVisible();
            }
            finally
            {
                programmExpand = false;
            }
            // Очищаем список.
            listView.Clear();
            // Отключаем кнопки, которые работают со списком файлов.
            CanShowInfo(false);
            CanCopyCutDelete(false);
            // Очищаем список файлов и заполняем его актуальными значениями.
            var items = new List<ListViewItem>();
            try
            {
                // Получаем информацию о директории.
                var dirInfo = new DirectoryInfo(path);
                // Загружаем поддиректории.
                foreach (var info in dirInfo.GetDirectories())
                    if (toolBtnShowHidden.Checked || (info.Attributes & FileAttributes.Hidden) == 0)
                    {
                        var key = GetDirIconPath(info.FullName);
                        LoadAssociatedIcon(imageList32, info.FullName, key, IconSize.Large);
                        var item = new ListViewItem(info.Name, key);
                        items.Add(item);
                        item.Tag = (ShortFileInfo)info;
                    }
                // Загружаем файлы.
                foreach (var info in dirInfo.GetFiles())
                    if (toolBtnShowHidden.Checked || (info.Attributes & FileAttributes.Hidden) == 0)
                    {
                        String key;
                        switch (info.Extension)
                        {
                            case ".ico":
                            case ".exe":
                                key = info.FullName; // Для этих типов файлов нельзя кэшировать иконки.
                                break;
                            case "":
                                key = "_default_file_"; // Для пустого ключа иконки не будут отображаться списке.
                                break;
                            default:
                                key = info.Extension;
                                break;
                        }
                        LoadAssociatedIcon(imageList32, info.FullName, key, IconSize.Large);
                        var item = new ListViewItem(info.Name, key);
                        items.Add(item);
                        item.Tag = (ShortFileInfo)info;
                    }
                // Отображаем путь в адресной строке.
                editNavigate.Text = path;
               }
            catch (ArgumentException)
            {
            }
            catch (UnauthorizedAccessException)
            {
            }
            //catch (PathTooLongException)
            //{
            //}
            catch (IOException)
            {
            }
            finally
            {
                listView.Items.AddRange(items.ToArray());
            }
            // Запускаем слежение за файлами текущего каталога.
            fileWatcher.Path = path;
            fileWatcher.EnableRaisingEvents = true;

            counter.Stop();
            toolStatusBar.Text = String.Format(Resources.ELEMENTS_COUNT, listView.Items.Count, counter.ElapsedMilliseconds);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="path"></param>
        private void OpenFile(String path)
        {
            try
            {
                if (frmTextEditor.IsSupportedFormat(path))
                    frmTextEditor.OpenTextFile(path);
                else
                    System.Diagnostics.Process.Start(path);
            }
            catch (Exception ex)
            {
                MessageBox.Show(String.Format(Resources.ERROR_OPEN_FILE, path, ex.Message), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        /// <summary>
        /// Сбрасывает кэш, чтобы при следующей загрузке нода список директорий обновился.
        /// </summary>
        /// <param name="node"></param>
        /// <param name="childs"></param>
        private void ResetCache(TreeNode node, Boolean childs)
        {
            if (node == null)
                return;
            node.Tag = null;
            if (!childs)
                return;
            foreach (TreeNode n in node.Nodes)
                ResetCache(n, childs);
        }

        /// <summary>
        /// Обновление содержимого текущей папки.
        /// </summary>
        private void RefreshCurrentPath()
        {
            var node = treeView.SelectedNode;
            if (node == null)
                node = GetNodeByPath(history.Path, true);
            if (node == null)
                return;
            ResetCache(node, false);
            OpenFolder(history.Path);
        }

        // Изменение текущего пути истории навигации.
        private void OnNavigateChange()
        {
            OpenFolder(history.Path);
            toolBtnBack.Enabled = history.CanBack();
            toolBtnForward.Enabled = history.CanForward();
            toolBtnUp.Enabled = history.CanUp();
            CanCreateNewElement(!String.IsNullOrEmpty(history.Path));
        }

        private void editNavigate_KeyPress(object sender, KeyPressEventArgs e)
        {
            if (e.KeyChar == '\r')
            {
                e.Handled = true;
                var edit = sender as ToolStripSpringTextBox;
                if (edit != null)
                {
                    var path = edit.Text;
                    // Если указанный путь относительный, необходимо привести его к полному виду.
                    if (!Path.IsPathRooted(path))
                        path = Path.Combine(history.Path, path);
                    // Преобразовываем путь в полную форму.
                    path = Path.GetFullPath(path);
                    // Проверяем путь.
                    switch (GetPathType(path))
                    {
                        case PathType.Directory:
                            history.Path = path;
                            break;
                        case PathType.File:
                            OpenFile(path);
                            break;
                        default:
                            MessageBox.Show(String.Format(Resources.ERROR_GOTO_PATH, path), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                            break;
                    }
                }
            }
        }

        private void CanCreateNewElement(Boolean value)
        {
            toolBtnCreateFile.Enabled = value;
            toolBtnCreateFolder.Enabled = value;
            itemCreateFile.Enabled = value;
            itemCreateFolder.Enabled = value;
        }

        private void CanCopyCutDelete(Boolean value)
        {
            toolBtnCopy.Enabled = value;
            toolBtnCut.Enabled = value;
            toolBtnDelete.Enabled = value;
            itemCopy.Enabled = value;
            itemCut.Enabled = value;
            itemDelete.Enabled = value;
        }

        private void CanPaste(Boolean value)
        {
            toolBtnPaste.Enabled = value;
            itemPaste.Enabled = value;
        }

        private void CanShowInfo(Boolean value)
        {
            toolBtnInfo.Enabled = value;
            itemInfo.Enabled = value;
        }

        private void treeViewFolders_AfterSelect(object sender, TreeViewEventArgs e)
        {
            history.Path = e.Node.FullPath;
        }

        private void treeViewFolders_NodeMouseClick(object sender, TreeNodeMouseClickEventArgs e)
        {
            // Если нод закрыт и пользователь кликает по тексту, открываем нод.
            if (e.Node.IsExpanded || e.Button != MouseButtons.Left)
                return;
            var hit = treeView.HitTest(e.Location);
            if (hit.Location == TreeViewHitTestLocations.Label || hit.Location == TreeViewHitTestLocations.Image)
                e.Node.Expand();
        }

        private void treeViewFolders_BeforeExpand(object sender, TreeViewCancelEventArgs e)
        {
            // Если нод раскрывается непрограммно, выделяем его, что приведет к переходу по пути, указанному в ноде. 
            if (!programmExpand)
                treeView.SelectedNode = e.Node;
        }

        private void toolBtnShowHidden_Click(object sender, EventArgs e)
        {
            foreach (TreeNode node in treeView.Nodes)
                ResetCache(node, true);
            if (!String.IsNullOrEmpty(history.Path))
                OpenFolder(history.Path);
        }

        private void toolBtnBack_Click(object sender, EventArgs e)
        {
            history.Back();
        }

        private void toolBtnForward_Click(object sender, EventArgs e)
        {
            history.Forward();
        }

        private void toolBtnUp_Click(object sender, EventArgs e)
        {
            history.Up();
        }

        private void toolBtnRefresh_Click(object sender, EventArgs e)
        {
            RefreshCurrentPath();
        }

        private void listViewFiles_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            if (e.Button != MouseButtons.Left)
                return;
            var item = listView.GetItemAt(e.X, e.Y);
            if (item == null)
                return;
            var info = item.Tag as ShortFileInfo;
            if (info == null)
                return;
            if (info.IsDirectory)
                history.Path = info.Path;
            else
                OpenFile(info.Path);
        }

        private void itemModeTile_Click(object sender, EventArgs e)
        {
            foreach (ToolStripMenuItem item in toolBtnViewMode.DropDownItems)
                item.Checked = sender == item;
        }

        private void itemModeTile_CheckedChanged(object sender, EventArgs e)
        {
            var temp = sender as ToolStripMenuItem;
            if (temp != null && temp.Checked)
                listView.View = View.Tile;
        }

        private void itemModeList_CheckedChanged(object sender, EventArgs e)
        {
            var temp = sender as ToolStripMenuItem;
            if (temp != null && temp.Checked)            
                listView.View = View.List;
        }

        private void toolBtnInfo_Click(object sender, EventArgs e)
        {
            var items = listView.SelectedItems;
            if (items == null || items.Count < 1)
                return;
            var info = items[0].Tag as ShortFileInfo;
            if (info == null)
                return;
            var frmInfo = new frmFileInfo(info.Path);
            frmInfo.Show();
        }

        private void listViewFiles_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
        {
            ShortFileInfo info = null;
            if (listView.SelectedItems.Count > 0)
                info = listView.SelectedItems[0].Tag as ShortFileInfo;
            CanShowInfo(info != null && !info.IsDirectory);
            CanCopyCutDelete(listView.SelectedItems.Count > 0);
        }

        private void toolBtnCreateFolder_Click(object sender, EventArgs e)
        {
            var name = Resources.NEW_DIRECTORY;
            if (frmInputQuery.InputQuery(Text, Resources.NEW_DIRECTORY_NAME, ref name))
            {
                var path = history.Path + name;
                // TODO: ситуация, когда каталог уже существует.
                try
                {
                    Directory.CreateDirectory(path);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(String.Format(Resources.ERROR_CREATE_FOLDER, path, ex.Message), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private void toolBtnCreateFile_Click(object sender, EventArgs e)
        {
            var name = Resources.NEW_FILE;
            if (frmInputQuery.InputQuery(Text, Resources.NEW_FILE_NAME, ref name))
            {
                var path = history.Path + name;
                // TODO: ситуация, когда файл уже существует.
                try
                {
                    using (File.Create(path))
                    {
                    }
                }
                catch (Exception ex)
                {
                    MessageBox.Show(String.Format(Resources.ERROR_CREATE_FILE, path, ex.Message), Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                }
            }
        }

        private void toolBtnDelete_Click(object sender, EventArgs e)
        {
            if (MessageBox.Show(String.Format(Resources.QUESTION_DELETE, listView.SelectedItems.Count), Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
                return;
            treeView.BeginUpdate();
            try
            {
                listView.BeginUpdate();
                try
                {
                    foreach (ListViewItem item in listView.SelectedItems)
                        try
                        {
                            var info = item.Tag as ShortFileInfo;
                            if (info.IsDirectory)
                            {
                                var dir = new DirectoryInfo(info.Path);
                                dir.Delete(true);
                            }
                            else
                            {
                                var file = new FileInfo(info.Path);
                                file.Delete();
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                        }                       
                }
                finally
                {
                    listView.EndUpdate();
                }
            }
            finally
            {
                treeView.EndUpdate();
            }
        }

        private void itemSortName_Click(object sender, EventArgs e)
        {
            foreach (ToolStripMenuItem item in toolBtnSort.DropDownItems)
                item.Checked = sender == item;
        }

        private void itemSortName_CheckedChanged(object sender, EventArgs e)
        {
            var temp = sender as ToolStripMenuItem;
            if (temp != null && temp.Checked)
                listView.ListViewItemSorter = new ListViewItemComparer(FileCompareType.ByName);
        }

        private void itemSortSize_CheckedChanged(object sender, EventArgs e)
        {
            var temp = sender as ToolStripMenuItem;
            if (temp != null && temp.Checked)
                listView.ListViewItemSorter = new ListViewItemComparer(FileCompareType.BySize);
        }

        private void itemSortDateCreate_CheckedChanged(object sender, EventArgs e)
        {
            var temp = sender as ToolStripMenuItem;
            if (temp != null && temp.Checked)
                listView.ListViewItemSorter = new ListViewItemComparer(FileCompareType.ByDateCreate);
        }

        private void itemSortDateModification_CheckedChanged(object sender, EventArgs e)
        {
            var temp = sender as ToolStripMenuItem;
            if (temp != null && temp.Checked)
                listView.ListViewItemSorter = new ListViewItemComparer(FileCompareType.ByDateModification);
        }

        private void fileSystemWatcherFolder_Renamed(object sender, RenamedEventArgs e)
        {
            // Ищем нод, связанный со старым именем каталога.
            var node = GetNodeByPath(e.OldFullPath, false);
            if (node == null)
                return;
            // Изменения происходят в открытом на данный момент каталоге, для их отображения надо просто перезагрузить текущий путь.
            if (treeView.SelectedNode == node.Parent)
                RefreshCurrentPath();
            else
                node.Text = Path.GetFileName(e.Name);
        }

        private void fileSystemWatcherFolder_Changed(object sender, FileSystemEventArgs e)
        {
            // Ищем родительский нод.
            var parent = GetNodeByPath(Path.GetDirectoryName(e.FullPath), false);
            if (parent == null)
                return;
            // Изменения происходят в открытом на данный момент каталоге, для отображения изменений надо просто перезагрузить текущий путь.
            if (treeView.SelectedNode == parent)
            {
                RefreshCurrentPath();
                return;
            }
            // В нод уже загружен список каталогов, необходимо вручную внести изменения.
            if (parent.IsInit())
                switch (e.ChangeType)
                {
                    case WatcherChangeTypes.Deleted:
                        var node = parent.Nodes.NodeByText(Path.GetFileName(e.FullPath));
                        if (node != null)
                            node.Nodes.Remove(node);
                        break;
                    case WatcherChangeTypes.Created:
                        var info = new DirectoryInfo(e.FullPath);
                        AddNode(parent.Nodes, info);
                        break;
                }
        }

        private void fileSystemWatcherFile_Renamed(object sender, RenamedEventArgs e)
        {
            RefreshCurrentPath();
        }

        private void fileSystemWatcherFile_Changed(object sender, FileSystemEventArgs e)
        {
            RefreshCurrentPath();
        }

        private void toolBtnFind_Click(object sender, EventArgs e)
        {
            var form = new frmFind();
            form.ShowDialog();
        }

        private void toolBtnCopy_Click(object sender, EventArgs e)
        {
            // Выделен хотя бы один элемент?
            if (listView.SelectedItems.Count == 0)
                return;
            // Определяем тип операции.
            if (sender == toolBtnCopy || sender == itemCopy)
                fileOperation = FileOperation.Copy;
            else if (sender == toolBtnCut || sender == itemCut)
                fileOperation = FileOperation.Cut;
            else
                return;
            // Перезаписываем список файлов.
            clipbList.Clear();
            foreach (ListViewItem item in listView.SelectedItems)
                clipbList.Add(item.Tag as ShortFileInfo);
            // Активируем кнопку "Вставить".
            CanPaste(true);
        }

        private void toolBtnPaste_Click(object sender, EventArgs e)
        {
            treeView.BeginUpdate();
            try
            {
                listView.BeginUpdate();
                try
                {
                    var path = history.Path;
                    // Выполняем заданную операцию.
                    switch (fileOperation)
                    {
                        // TODO: Обрабатывать ситуацию, когда файлы уже имеются в конечном каталоге.
                        case FileOperation.Copy:
                            foreach (var item in clipbList)
                                try
                                {
                                    File.Copy(item.Path, path + Path.GetFileName(item.Path), false);
                                }
                                catch (Exception ex)
                                {
                                    MessageBox.Show(ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                                }
                            break;
                        case FileOperation.Cut:
                            foreach (var item in clipbList)
                                try
                                {
                                    File.Move(item.Path, path + Path.GetFileName(item.Path)); // TODO: maybe File.Replace?
                                }
                                catch (Exception ex)
                                {
                                    MessageBox.Show(ex.Message, Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
                                }
                            break;
                    }
                }
                finally
                {
                    listView.EndUpdate();
                }
            }
            finally
            {
                treeView.EndUpdate();
            }            
        }
    }

    enum PathType
    {
        Error,
        File,
        Directory
    }

    enum FileCompareType
    {
        ByName,
        BySize,
        ByDateCreate,
        ByDateModification
    }

    enum FileOperation
    {
        None,
        Copy,
        Cut
    }

    class ShortFileInfo
    {
        public static implicit operator ShortFileInfo(DirectoryInfo info)
        {
            var temp = new ShortFileInfo();
            temp.IsDirectory = true;
            temp.Path = info.FullName;
            temp.Size = Int64.MaxValue;
            temp.DateCreate = info.CreationTime;
            temp.DateModification = info.LastWriteTime;
            return temp;
        }

        public static implicit operator ShortFileInfo(FileInfo info)
        {
            var temp = new ShortFileInfo();
            temp.IsDirectory = false;
            temp.Path = info.FullName;
            temp.Size = info.Length;
            temp.DateCreate = info.CreationTime;
            temp.DateModification = info.LastWriteTime;
            return temp;
        }

        public Boolean IsDirectory
        {
            get;
            private set;
        }

        public String Path
        {
            get;
            private set;
        }

        public Int64 Size
        {
            get;
            private set;
        }

        public DateTime DateCreate
        {
            get;
            private set;
        }

        public DateTime DateModification
        {
            get;
            private set;
        }
    }

    class ListViewItemComparer : IComparer
    {
        private readonly FileCompareType compareType;

        public ListViewItemComparer(FileCompareType compareType)
        {
            this.compareType = compareType;
        }

        public int Compare(object x, object y)
        {
            var item1 = x as ListViewItem;
            var item2 = y as ListViewItem;
            if (item1 == null || item2 == null)
                return 0;
            var info1 = item1.Tag as ShortFileInfo;
            var info2 = item2.Tag as ShortFileInfo;
            if (info1 == null || info2 == null)
                return 0;
            if (info1.IsDirectory != info2.IsDirectory)
                return info1.IsDirectory ? -1: 1;
            else
                switch (compareType)
                {
                    case FileCompareType.ByName:
                        return String.Compare(Path.GetFileNameWithoutExtension(info1.Path), Path.GetFileNameWithoutExtension(info2.Path), true, CultureInfo.CurrentCulture);
                    case FileCompareType.BySize:
                        return Math.Sign(info1.Size - info2.Size);
                    case FileCompareType.ByDateCreate:
                        return DateTime.Compare(info1.DateCreate, info2.DateCreate);
                    case FileCompareType.ByDateModification:
                        return DateTime.Compare(info1.DateModification, info2.DateModification);
                    default:
                        return 0;
                }
        }
    }

}
